<?php
/**
 * 数据库驱动管理器
 * 
 * $Id: data.php 130 2010-10-10 16:47:37Z Administrator $
 * $Revision: 130 $
 * $Author: Administrator $
 * $Date: 2010-10-10 16:47:37 +0000 (Sun, 10 Oct 2010) $
 * 提供相同的程序界面访问数据库，
 * 目录最佳支持Mysql
 * 其它可以通过ODBC进行驱动的数据库
 *
 * @author glzaboy<glzaboy@163.com>
 * @package fastlib
 * @subpackage classes
 * @copyright 2005-2010 (c) glzaboy
 */
if (! defined ( '__FASTLIB__' )) {
	if (__DEBUG__) {
		die ( sprintf ( 'Forbidden 403 at file %s on line %d', __FILE__, __LINE__ ) );
	} else {
		die ( 'Forbidden 403' );
	}
}
/**
 * 加载网站数据库映射
 */
if (! defined ( '__TABLE__' )) {
	if (file_exists ( getSiteCurrent ( true ) . '/data/site.table.php' )) {
		/**
		 * 将一个数据表定义载入到系统中,它同时会删除去这个变量的定义
		 * 
		 * 数据映射定义方法
		 * <code>
		 * <?php
		 * $nv = array(
		 * 'table' => 'test_nv' ,//表名前缀使用站点 配置sql_prefix文件中的前缀
		 * array(
		 * 'id'		=> 'I' , //字段名和类型数值为(I)其它为(C)
		 * 'name' 		=> 'I',//字段名和类型数值为(I)其它为(C)
		 * )
		 * );
		 * loadTable('nv',$nv); //载入
		 * ?>
		 * </code>
		 *
		 * @param string $name 载入后的映射名称
		 * @param mixed $var 数据表信息 
		 */
		function loadTable($name, &$var) {
			global $__TABLE;
			$__TABLE [$name] = $var;
			unset ( $var );
		}
		global $__TABLE;
		require (getSiteCurrent ( true ) . '/data/site.table.php');
	}
	define ( '__TABLE__', true );
}
/**
 * 数据库驱动管理器
 *
 * 本数据管理器可以使用在php5上。但是本数据库驱动管理器支持多表查询，
 * 支持各种join方法，在项目中不要混用数据库管理器的不同版本。本文件将除了重大bUG
 * 外不再更新。
 *
 * @author 顾留中 <glzaboy@163.com>
 */
class data {
	/**
	 * 出错中止运行
	 *
	 * @var bool
	 */
	private $halt = false;
	/**
	 * 日志记录对象
	 *
	 * @var CLASSOBJECT
	 */
	private $logObj = null;
	/**
	 * 数据库对象驱动
	 *
	 * @var object
	 */
	private $driver = null;
	/**
	 * 数据库驱动名称
	 *
	 * @var object
	 */
	public $driverName = null;
	/**-----数据语句相关的操作----------**/
	/**
	 * 系统数据表前缀
	 *
	 * @var String
	 */
	private $data_prefix = null;
	/**
	 * SQL语句中使用的数据库表前缀
	 *
	 * @var String
	 */
	private $sql_prefix = null;
	/**
	 * 数据关键映射
	 *
	 * @var mixed
	 */
	private $matrix = array ();
	/**
	 * 数据库连接字符集
	 *
	 * @var string
	 */
	private $charset = null;
	/**-----数据语句相关的操作----------**/
	/**
	 * 载入数据库类
	 * 
	 * 如果载入成功后返回可以使用的数据库类
	 *
	 * @param mixed $datacfg 数据库配置
	 * @return bool
	 */
	public function __construct($datacfg = null) {
		if ($datacfg) {
			return $this->loadCfg ( $datacfg );
		}
		return true;
	}
	public function __destruct() {
		if ($this->driver) {
			$this->close ();
		}
	}
	/**
	 * 载入或重新初始化数据库连接
	 * 
	 * 对于有一些数据库如MYSQL，无法同时连接 同一服务器上的多个数据库，
	 * 可以先使用close方法关闭原来的数据库连接。使用本函数连接新的数据库。
	 *
	 * @param mixed $datacfg 数据库连接配置
	 * @return bool;
	 */
	public function loadCfg($datacfg) {
		$driver = __CLASS__ . '_' . preg_replace ( "/[^a-zA-Z0-9]+/", '', $datacfg ['driver'] );
		if (class_exists ( $driver )) {
			$this->driver = new $driver ();
		} elseif (file_exists ( __FASTSYSTEM__ . 'driver/dbi/' . trim ( $driver ) . '.php' )) {
			FastLoad::fastinclude ( 'driver/dbi/' . trim ( $driver ) . '.php', 'require' );
			$this->putlog ( 'load database driver ' . $driver, __FILE__, __LINE__ );
			$this->driver = new $driver ();
		} else {
			$this->putlog ( 'require data driver ' . $driver . ' but can\'t fine it.', __FILE__, __LINE__ );
			return false;
		}
		$this->driverName = preg_replace ( "/[^a-zA-Z0-9]+/", '', $datacfg ['driver'] );
		$this->sql_prefix = @$datacfg ['sql_prefix'] ? $datacfg ['sql_prefix'] : null;
		$this->data_prefix = @$datacfg ['data_prefix'] ? $datacfg ['data_prefix'] : null;
		if (! $this->driver->connect ( $datacfg )) {
			$this->halt = true;
			return false;
		}
		if (@$datacfg ['charset']) {
			$this->setCharset ( @$datacfg ['charset'] );
			$this->charset = $datacfg ['charset'];
		}
		return true;
	}
	/**
	 * 设置数据库连接编码
	 *
	 * @param String $charset 编码
	 * @return 设置是否成功
	 */
	private function setCharset($charset) {
		return $this->driverCall ( 'setCharset', array ($charset ) );
	}
	/**
	 * 关闭PHP与数据库之间的连接
	 *
	 * 一般情况下请求结束后服务器自动关闭数据库，由于某些数据库的特殊性如mysql，
	 * 当要连接多个同一服务器上的数据库时可能要关闭数据库重新连接。
	 *
	 * @return 关闭数据库结果。
	 */
	public function close() {
		if ($this->driver) {
			$this->driver->close ();
			$this->driver = null;
		}
		return true;
	}
	/**
	 * 返回正在使用的数据库版本
	 *
	 * *)本涵数不是对所有数据库都有效
	 *
	 * @return String 版本信息
	 */
	public function getServerVersion() {
		return $this->driver->getServerVersion ();
	}
	/**
	 * 驱动内核调用
	 *
	 * @param String $charset 编码
	 * @return 设置是否成功
	 */
	public function driverCall($func, $arguments = null) {
		if (method_exists ( $this->driver, $func )) {
			if ($arguments == null) {
				$arguments = array ();
			}
			return call_user_func_array ( array (&$this->driver, $func ), $arguments );
		} else {
			$this->putlog ( 'driverCall method ' . $func . ' not exists in ' . get_class ( $this->driver ), __FILE__, __LINE__ );
		}
		return null;
	}
	/**
	 * 数据导入
	 *
	 * @param string $sql SQL
	 * @param string $sqlsplit SQL 分隔
	 * @return true
	 */
	public function Import($sql, $sqlsplit = ';') {
		$sql = strtr ( $sql, array ($this->sql_prefix => $this->data_prefix, '[charset]' => $this->charset ) );
		$this->driverCall ( 'Import', array ($sql, $sqlsplit ) );
		return true;
	}
	/**
	 * 设置默认操作表
	 *
	 * @param string $table 表的映射关系
	 * @param string $name 表别名
	 * @return bool 设置是否成功
	 */
	public function setTable($table, $name = null) {
		$clearArr = $this->clearjoin ();
		array_unshift ( $clearArr, $this->clearupjoin () );
		$key = $name ? $name : 'table' . rand ( 0, 100 );
		if (is_string ( $table ) && $this->jion ( array ($key => $table ), null, null, null )) {
			return true;
		}
		$this->putlog ( 'data table ' . $table . ' not define in site.table.php.!', __FILE__, __LINE__ );
		//失败回存原来的数据映射
		if (is_array ( $clearArr )) {
			foreach ( $clearArr as $v ) {
				$this->jion ( $v, null, null, null, true );
			}
		}
		return null;
	}
	/**
	 * 判断表是否在在
	 * 
	 * 返回null为无效
	 * 
	 * @param string $table 表名
	 * @param bool $prefix 是否前缀替换
	 * @return bool 表是否在在
	 */
	public function isTable($table, $prefix = 1) {
		if ($prefix) {
			$table = $this->data_prefix . $table;
		}
		return $this->driverCall ( 'isTable', array ($table ) );
	}
	/**
	 * 设置一个表join 操作 
	 * 
	 * 如果要join 多个表可以多次调用本函数
	 *
	 * @param mixed $matrix 表映射关系或映射关系名,只有内部调用时才会使用映射关系。
	 * @param string $join join类型（left right innser ...）依据不同的数据库而改变。
	 * @param Array $fieldArr 字段和别名关系。如array('姓名'=>'name')给name字段一个叫姓名的别名
	 * @param string $onwhereStr join后的表连接 如  name='my'.name 就是本表中的name 和my表别名的name作连接
	 * @param bool $prefix 是否已经预处理
	 * @return unknown
	 */
	public function jion($matrix, $join, $fieldArr, $onwhereStr, $prefix = false) {
		if (! $prefix) {
			if ($join && count ( $this->matrix ) == 0) {
				//没有表就直接调用join不合法
				return false;
			}
			global $__TABLE;
			if (is_string ( $matrix )) {
				$matrix = array ($matrix );
			} elseif (! is_array ( $matrix )) {
				$this->putlog ( "data.join.arguments.1.matrix only string or array.!", __FILE__, __LINE__ );
				return null;
			}
			if (! is_string ( $key = array_shift ( array_keys ( $matrix ) ) )) {
				$key = null;
			}
			$matrix = array_shift ( $matrix );
			if (! isset ( $__TABLE [$matrix] )) {
				return false;
			}
			$matrix = $__TABLE [$matrix];
			$matrixfield = $matrix [0];
			$matrixArr = array ();
			$matrixArr ['table'] = str_replace ( $this->sql_prefix, $this->data_prefix, $matrix ['table'] );
			$matrixArr ['AS'] = $key;
			if ($join) {
				$matrixArr ['join'] = strtolower ( $join );
				if (is_array ( $fieldArr )) {
					$field = array ();
					foreach ( $fieldArr as $k => $v ) {
						if (substr ( $v, 0, 5 ) == 'clac:') {
							$v = substr ( $v, 5 );
							$field [$v] ['clac'] = true;
						} elseif ($matrixfield [$v]) {
							$field [$v] ['type'] = $matrixfield [$v];
						} else {
							continue;
						}
						if (is_string ( $k )) {
							$field [$v] ['AS'] = $k;
						}
					}
					$matrixArr ['field'] = $field;
				}
				if (is_string ( $onwhereStr )) {
					$matrixArr ['onwhere'] = $onwhereStr;
				} else {
					return false;
				}
			} else {
				$matrixArr ['field'] = $matrixfield;
			}
			array_push ( $this->matrix, $matrixArr );
			return true;
		} else {
			return array_push ( $this->matrix, $matrix );
		}
	}
	/**
	 * 清除所有的join操作
	 *
	 * @return true
	 */
	public function clearjoin() {
		$clearArr = array ();
		while ( count ( $this->matrix ) > 1 ) {
			array_unshift ( $clearArr, $this->clearupjoin () );
		}
		return $clearArr;
	}
	/**
	 * 清除前一次jion操作
	 *
	 * @return 清除的结果
	 */
	public function clearupjoin() {
		return array_pop ( $this->matrix );
	}
	/**
	 * 查询数据库数据
	 *
	 * @param mixed $sql_info 数据库的表字段，或SQL
	 * @param String $where	条件语句
	 * @param bool $single 单表查询
	 * @param bool $onlynum	是否仅返回条数
	 * @param Int $offset　偏移
	 * @param Int $num　返回条数
	 * @return Array or Int 如果$onlynum=0, 返回数据，否则返回条数
	 */
	public function Select($field = null, $where = null, $single = false, $onlynum = false, $offset = null, $num = null) {
		$matrix = $this->matrix [0] ? $this->matrix [0] : null;
		if ($matrix == null) {
			$this->putlog ( "You not set a table,please use setTable!", __FILE__, __LINE__ );
			return false;
		}
		$matrixArr = $this->matrix;
		if ($field == null) {
			if (is_array ( $matrixArr [0] ['field'] )) {
				$field = array ();
				foreach ( $matrixArr [0] ['field'] as $k => $v ) {
					$field [$k] ['type'] = $v;
				}
				$matrixArr [0] ['field'] = $field;
				unset ( $field );
			} else {
				return null;
			}
		} elseif (is_array ( $field )) {
			$matrixArr [0] ['field'] = array ();
			foreach ( $field as $k => $v ) {
				if (substr ( $v, 0, 5 ) == 'clac:') {
					$v = substr ( $v, 5 );
					$matrixArr [0] ['field'] [$v] = array ('clac' => true );
				} elseif ($matrix ['field'] [$v]) {
					$matrixArr [0] ['field'] [$v] = array ('type' => $matrix ['field'] [$v] );
				} else {
					continue;
				}
				if (is_string ( $k )) {
					$matrixArr [0] ['field'] [$v] ['AS'] = $k;
				}
			}
		} elseif (is_string ( $field )) {
			$matrixArr = $field;
		} else {
			$this->putlog ( "data.Select error!", __FILE__, __LINE__ );
			return false;
		}
		//处理是单表还是join
		if ($single) {
			while ( count ( $matrixArr ) > 1 ) {
				array_pop ( $matrixArr );
			}
		}
		$where = strtr ( $where, array ($this->sql_prefix => $this->data_prefix, '\[' => '\133', '\]' => '\135' ) );
		return $this->driver->Select ( $matrixArr, $where, $onlynum, $offset, $num );
	}
	/**
	 * 插入数据记录
	 *
	 * @param Array $Data　记录的名值对，注意这会做一定的运算 
	 * @param string $table 临时使用映射关系
	 * @return Int 插入后ID
	 */
	public function Insert($Data, $table = null) {
		if ($this->halt) {
			$this->putlog ( "Driver has been halt self.It's looks a error!", __FILE__, __LINE__ );
			return null;
		}
		if (! $Data) {
			$this->putlog ( "Data is empty!", __FILE__, __LINE__ );
			return null;
		}
		if (is_string ( $table )) {
			global $__TABLE;
			if (! $__TABLE [$table]) {
				$this->putlog ( "Insert argument.2({$table}) is not exists!!", __FILE__, __LINE__ );
				return false;
			}
			$matrix = str_replace ( $this->sql_prefix, $this->data_prefix, $__TABLE [$table] );
			$matrix ['field'] = $matrix [0];
			unset ( $matrix [0] );
		}
		$matrix = isset ( $matrix ) ? $matrix : ($this->matrix [0] ? $this->matrix [0] : null);
		if (! $matrix) {
			$this->putlog ( "You not set a table,please use setTable!", __FILE__, __LINE__ );
			return null;
		}
		foreach ( $Data as $k => $v ) {
			if (isset ( $matrix ['field'] [$k] )) {
				if (substr ( $v, 0, 5 ) == 'clac:') {
					$matrix ['data'] [$k] = array ("type" => 'I', "data" => strtr ( substr ( $v, 5 ), array ('\[' => '\133', '\]' => '\135' ) ), 'clac' => true );
				} else {
					$matrix ['data'] [$k] = array ("type" => $matrix ['field'] [$k], "data" => ($matrix ['field'] [$k] == 'C') ? $v : preg_replace ( "/[^0-9.]+/", '', $v ) );
				}
			}
		}
		unset ( $matrix ['AS'], $matrix ['field'] );
		return $this->driver->Insert ( $matrix );
	}
	/**
	 * 更新数据记录
	 *
	 * @param Array $Data　记录的名值对，注意这会做一定的运算 
	 * @param string $where 更新条件
	 * @param string $table 临时使用映射关系
	 * @return bool 更新语句执行结果
	 */
	public function Update($Data, $where = null, $table = null) {
		if ($this->halt) {
			$this->putlog ( "Driver has been halt self.It's looks a error!", __FILE__, __LINE__ );
			return null;
		}
		if (! $Data) {
			$this->putlog ( "Data is empty!", __FILE__, __LINE__ );
			return null;
		}
		if (is_string ( $table )) {
			global $__TABLE;
			if (! $__TABLE [$table]) {
				$this->putlog ( "Update argument.3({$table}) is not exists!!", __FILE__, __LINE__ );
				return false;
			}
			$matrix = str_replace ( $this->sql_prefix, $this->data_prefix, $__TABLE [$table] );
			$matrix ['field'] = $matrix [0];
			unset ( $matrix [0] );
		}
		$matrix = isset ( $matrix ) ? $matrix : ($this->matrix [0] ? $this->matrix [0] : null);
		if (! $matrix) {
			$this->putlog ( "You not set a table,please use setTable!", __FILE__, __LINE__ );
			return null;
		}
		foreach ( $Data as $k => $v ) {
			if (isset ( $matrix ['field'] [$k] )) {
				if (substr ( $v, 0, 5 ) == 'clac:') {
					$matrix ['data'] [$k] = array ("type" => 'I', "data" => strtr ( substr ( $v, 5 ), array ('\[' => '\133', '\]' => '\135' ) ), 'clac' => true );
				} else {
					$matrix ['data'] [$k] = array ("type" => $matrix ['field'] [$k], "data" => ($matrix ['field'] [$k] == 'C') ? $v : preg_replace ( "/[^0-9.]+/", '', $v ) );
				}
			}
		}
		if ($where == null) {
			$where = null;
		} else {
			if (is_string ( $where )) {
				$where = strtr ( $where, array ($this->sql_prefix => $this->data_prefix, '\[' => '\133', '\]' => '\135' ) );
			} else {
				//出错
				return false;
			}
		}
		$matrix ['onwhere'] = $where;
		unset ( $matrix ['AS'], $matrix ['field'] );
		return $this->driver->Update ( $matrix );
	}
	/**
	 * 删除数据库数据
	 *
	 * @param String $where　删除条件
	 * @param Strng $table　要删除的表格，如果不填则使用最后一次setTable的结果
	 * @param bool $truncate 是否清空整个表
	 * @return bool 删除语句执行结果
	 */
	public function Delete($where = null, $table = null, $truncate = false) {
		if ($this->halt) {
			$this->putlog ( "Driver has been halt self.It's looks a error!", __FILE__, __LINE__ );
			return null;
		}
		if (is_string ( $table )) {
			global $__TABLE;
		}
		$matrix = isset ( $__TABLE [$table] ) ? $__TABLE [$table] : ($this->matrix [0] ? $this->matrix [0] : null);
		if (! $matrix) {
			$this->putlog ( "You not set a table,please use setTable!", __FILE__, __LINE__ );
			return null;
		}
		$matrixArr = array ();
		$matrixArr ['table'] = str_replace ( $this->sql_prefix, $this->data_prefix, $matrix ['table'] );
		if ($where === null && $truncate) {
			$matrixArr ['truncate'] = $truncate;
		} elseif ($where === null) {
			$where = '';
		} else {
			if (is_string ( $where )) {
				$where = strtr ( $where, array ($this->sql_prefix => $this->data_prefix, '\[' => '\133', '\]' => '\135' ) );
			} else {
				//出错
				return false;
			}
		}
		$matrixArr ['onwhere'] = $where;
		return $this->driver->Delete ( $matrixArr );
	}
	/**
	 * 记录日志
	 *
	 * @param String $message 日志内容
	 * @param String $file 调用文件名
	 * @param Int $line 调用文件行数
	 * @return bool 是否成功写入日志
	 */
	private function putlog($message, $file, $line) {
		if (! $this->logObj) {
			global $flibcfg;
			$this->logObj = new log ( $flibcfg ['logTimeFormat'], __CLASS__ . '.' . $flibcfg ['logFileName'] );
		}
		return $this->logObj->putlog ( $message, $file, $line );
	}
	/**sql.Adapter
	 * 返回本模块信息
	 *
	 * @return string
	 */
	public function printInfo() {
		$version = '$Revision: 130 $';
		$moudle = __CLASS__;
		$return = '';
		$return .= startTable ();
		$return .= printRow ( array (array ('class' => 'h', 'content' => '<center>' . $moudle . '</center>', 'colspan' => '2' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'author' ), array ('class' => 'v', 'content' => 'glzaboy' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'version' ), array ('class' => 'v', 'content' => $version ) ) );
		$return .= printRow ( array (array ('class' => 'h', 'colspan' => '2', 'content' => 'driver list' ) ) );
		$dir = dir ( __FASTSYSTEM__ . 'driver/dbi' );
		for($r = $dir->read (); $r;) {
			if (is_file ( __FASTSYSTEM__ . 'driver/dbi/' . $r ) && substr ( $r, 0, 5 ) === 'data_') {
				if (! class_exists ( substr ( $r, 0, - 4 ) )) {
					FastLoad::fastinclude ( 'driver/dbi/' . chop ( $r ), 'require' );
				}
				if (method_exists ( substr ( $r, 0, - 4 ), 'printInfo' )) {
					$return .= call_user_func ( array (substr ( $r, 0, - 4 ), 'printInfo' ) );
				} else {
					$return .= printRow ( array (array ('class' => 'e', 'content' => substr ( $r, 5, - 4 ) ), array ('class' => 'v', 'content' => 'the file info not exists.' ) ) );
				}
			}
			$r = $dir->read ();
		}
		$return .= endTable ();
		return $return;
	}
}